﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Windows.Forms;
using Helpers;
using Helpers.Language;
using Helpers.Properties;

namespace Launcher
{
    public class Updater
    {
        #region Class variables

        private BackgroundWorker UpdateWorker { get; set; }
        private string Version { get; set; }
        private ToolStripProgressBar launcherProgressBar;
        private ToolStripStatusLabel launcherProgressBarLabel;
        private Settings AppSettings = Helpers.Properties.Settings.Default;
        private Launcher.PostPatchingDelegate postPatchingDelegate { get; set; }

        #endregion

        #region Constructor

        public Updater(ToolStripProgressBar launcherProgressBar, ToolStripStatusLabel launcherProgressBarLabel,
                       Launcher.PostPatchingDelegate postPatchingDelegate)
        {
            this.postPatchingDelegate = postPatchingDelegate;
            this.launcherProgressBar = launcherProgressBar;
            this.launcherProgressBarLabel = launcherProgressBarLabel;
        }

        public void RunUpdater()
        {
            UpdateWorker = new BackgroundWorker();
            UpdateWorker.WorkerReportsProgress = true;
            UpdateWorker.DoWork += new DoWorkEventHandler(CheckForUpdates);
            UpdateWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(CompletedCheckingForUpdates);
            UpdateWorker.ProgressChanged += new ProgressChangedEventHandler(CheckingForUpdatesProgressChanged);
            UpdateWorker.RunWorkerAsync();
        }

        #endregion

        #region Checking for updates

        private void CheckForUpdates(object sender, DoWorkEventArgs e)
        {
            UpdateWorker.ReportProgress(0, LangRes.Get("CheckingForUpdates"));
            
            var request = WebRequest.Create(AppSettings.UpdaterUrl + "/GetVersion.php?GetVersion=App");

            try
            {
                UpdateWorker.ReportProgress(50, LangRes.Get("CheckingForUpdates"));

                var response = request.GetResponse();
                var dataStream = response.GetResponseStream();
                var reader = new StreamReader(dataStream);
                var responseFromServer = reader.ReadToEnd();

                reader.Close();
                if (dataStream != null) dataStream.Close();
                response.Close();

                UpdateWorker.ReportProgress(100, LangRes.Get("Complete"));

                var currentVersion = new Version(Assembly.GetExecutingAssembly().GetName().Version.ToString());
                var serverVersion = new Version(responseFromServer);



                if (currentVersion.CompareTo(serverVersion) < 0)
                {
                    if (MessageBox.Show(LangRes.Get("NewVersionAvailable"), LangRes.Get("Update"),
                                        MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                    {
                        UpdateWorker.ReportProgress(0, LangRes.Get("ComparingChecksums"));
                        var fileChecksumsArray = CompareChecksums();
                        UpdateWorker.ReportProgress(100, LangRes.Get("Complete"));
                        Version = responseFromServer;
                        UpdateProgram(fileChecksumsArray);
                    }
                }
            }
            catch(WebException ex)
            {
                switch (ex.Status)
                {
                    case WebExceptionStatus.Timeout:
                        ErrorHandling.ShowError(LangRes.Get("CouldNotConnect") + " \"" + ex.Message + "\"");
                        break;
                    case WebExceptionStatus.ConnectFailure:
                        ErrorHandling.ShowError(LangRes.Get("CouldNotConnect") + " \"" + ex.Message + "\"");
                        break;
                }
            }
            catch (Exception ex)
            {
                ErrorHandling.ShowError(ex.Message);
            }
        }

        private FileChecksum[] CompareChecksums()
        {
            var request = WebRequest.Create(AppSettings.UpdaterUrl + "/GetFileChecksums.php");

            try
            {
                var response = request.GetResponse();
                var dataStream = response.GetResponseStream();
                var reader = new StreamReader(dataStream);
                var responseFromServer = reader.ReadToEnd();

                reader.Close();
                if (dataStream != null) dataStream.Close();
                response.Close();
                var serverChecksums =
                    responseFromServer.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries).Select(
                        s => s.Split(new string[] { "::" }, StringSplitOptions.None)).Select(cf => new FileChecksum()
                                                                                                     {
                                                                                                         Checksum = cf[0].ToUpper(),
                                                                                                         File = cf[1],
                                                                                                         Status = FilePatchStatus.None
                                                                                                     }).ToList();

                var files = FileAndFolderProcessing.GetFiles(Directory.GetCurrentDirectory());

                var patchFiles = new List<FileChecksum>();

                foreach (var file in files)
                {
                    var localfile = file.Replace(Directory.GetCurrentDirectory() + "\\", "");
                    var fileCountToServer = 0;
                    foreach (var serverChecksum in serverChecksums)
                    {
                        if (localfile == serverChecksum.File)
                        {
                            if (FileAndFolderProcessing.GetChecksum(file) != serverChecksum.Checksum)
                            {
                                patchFiles.Add(new FileChecksum()
                                                   {
                                                       Checksum = serverChecksum.Checksum,
                                                       File = serverChecksum.File,
                                                       Status = FilePatchStatus.Download
                                                   });
                            }
                            else
                            {
                                patchFiles.Add(new FileChecksum()
                                {
                                    Checksum = serverChecksum.Checksum,
                                    File = serverChecksum.File,
                                    Status = FilePatchStatus.None
                                });
                            }
                        }
                        else
                        {
                            fileCountToServer++;
                        }
                    }
                    if (fileCountToServer == serverChecksums.Count)
                    {
                        patchFiles.Add(new FileChecksum()
                        {
                            Checksum = "",
                            File = localfile,
                            Status = FilePatchStatus.Delete
                        });
                    }
                }
                var launcherFiles = AppSettings.LauncherFiles.Split(';');
                var launcherPatch = patchFiles.Where(patchFile => launcherFiles.Contains(patchFile.File) && patchFile.Status == FilePatchStatus.Download).Any();
                foreach (var t in patchFiles.Where(t => launcherPatch && launcherFiles.Contains(t.File)))
                {
                    t.Status = FilePatchStatus.Download;
                }
                return patchFiles.ToArray();
            }
            catch (Exception ex)
            {
                ErrorHandling.ShowError(ex.Message);
            }
            return null;
        }

        private void CheckingForUpdatesProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.launcherProgressBar.Value = e.ProgressPercentage;
            this.launcherProgressBarLabel.Text = e.UserState as String;
        }

        private void CompletedCheckingForUpdates(object sender, RunWorkerCompletedEventArgs e)
        {
            postPatchingDelegate();
        }

        #endregion

        #region Updating

        //downloading patch
        private void UpdateProgram(FileChecksum[] fileChecksums)
        {
            try
            {


                if (!Directory.Exists(AppSettings.UpdateDlTempPath))
                {
                    Directory.CreateDirectory(AppSettings.UpdateDlTempPath);
                }
                UpdateWorker.ReportProgress(0, LangRes.Get("DownloadingUpdates"));

                var downloadedFileCount = 0;

                foreach (var fileChecksum in fileChecksums)
                {
                    switch (fileChecksum.Status)
                    {
                        case FilePatchStatus.Download:
                            {
                                DownloadFile(AppSettings.UpdaterUrl + "/" + AppSettings.PatchLocation + "/" + fileChecksum.File,
                                             AppSettings.UpdateDlTempPath + "/" + fileChecksum.File);

                                downloadedFileCount++;
                                var progressPercentage =
                                    (int)(((double)(downloadedFileCount) / (double)fileChecksums.Length) * 100);

                                UpdateWorker.ReportProgress(progressPercentage, LangRes.Get("DownloadingUpdates"));
                            }
                            break;
                        case FilePatchStatus.Delete:
                            {
                                FileAndFolderProcessing.DeleteFile(fileChecksum.File);
                            }
                            break;
                    }
                }

                this.launcherProgressBarLabel.Text = LangRes.Get("UpdatesDownloaded");

                InstallUpdates();

                launcherProgressBarLabel.Text = LangRes.Get("UpdatesInstalled");
            }
            catch (Exception ex)
            {
                ErrorHandling.ShowError(ex.Message);
            }
        }

        //method for downloading a file from HTTP
        private void DownloadFile(string fileDlUrl, string fileDlPath)
        {
            try
            {
                var url = new Uri(fileDlUrl);
                var request = (HttpWebRequest)WebRequest.Create(url);
                var response = (HttpWebResponse)request.GetResponse();
                response.Close();
                var patchSize = response.ContentLength;
                using (var client = new WebClient())
                {
                    using (var streamRemote = client.OpenRead(new Uri(fileDlUrl)))
                    {
                        using (
                            Stream streamLocal = new FileStream(fileDlPath, FileMode.Create, FileAccess.Write,
                                                                FileShare.None))
                        {
                            var byteSize = 0;
                            var byteBuffer = new byte[patchSize];
                            while ((byteSize = streamRemote.Read(byteBuffer, 0, byteBuffer.Length)) > 0)
                            {
                                streamLocal.Write(byteBuffer, 0, byteSize);
                            }
                            streamLocal.Close();
                        }
                        streamRemote.Close();
                    }
                }
            }
            catch (Exception ex)
            {
                ErrorHandling.ShowError(ex.Message);
            }
        }
        //copying patch files
        private void InstallUpdates()
        {
            UpdateWorker.ReportProgress(0, LangRes.Get("InstallingUpdate"));
            try
            {
                var fileList = Directory.GetFiles(AppSettings.UpdateDlTempPath);

                var launcherFiles = AppSettings.LauncherFiles.Split(';');
                var launcherPatch = launcherFiles.Any(launcherFile => fileList.Contains(AppSettings.UpdateDlTempPath + "\\" + launcherFile));

                if (!launcherPatch)
                {
                    var target = new DirectoryInfo(Directory.GetCurrentDirectory());
                    var source = new DirectoryInfo(AppSettings.UpdateDlTempPath);
                    FileAndFolderProcessing.CopyAll(source, target);
                    CleanupTemp();
                }
                else
                {
                    var tempLauncher = new Process();
                    tempLauncher.StartInfo.FileName = Directory.GetCurrentDirectory() + "\\" + AppSettings.UpdateDlTempPath +
                                                      "\\Launcher.exe";
                    tempLauncher.StartInfo.Arguments = " " + AppSettings.SelfUpdateArgument;
                    tempLauncher.StartInfo.WorkingDirectory = Directory.GetCurrentDirectory() + "\\" +
                                                              AppSettings.UpdateDlTempPath;
                    tempLauncher.Start();
                    Application.Exit();
                }
            }
            catch (Exception ex)
            {
                ErrorHandling.ShowError(ex.Message);
            }
            finally
            {
                UpdateWorker.ReportProgress(100, LangRes.Get("Complete"));
            }
        }

        //Self updater invoker
        public void RunSelfUpdater()
        {
            UpdateWorker = new BackgroundWorker();
            UpdateWorker.WorkerReportsProgress = true;
            UpdateWorker.DoWork += new DoWorkEventHandler(SelfUpdater);
            UpdateWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(CompletedCheckingForUpdates);
            UpdateWorker.ProgressChanged += new ProgressChangedEventHandler(CheckingForUpdatesProgressChanged);
            UpdateWorker.RunWorkerAsync();
        }

        //Self updater
        private void SelfUpdater(object sender, DoWorkEventArgs doWorkEventArgs)
        {
            try
            {
                var currentDir = Directory.GetCurrentDirectory().Replace("\\" + AppSettings.UpdateDlTempPath, "");
                var target = new DirectoryInfo(currentDir);

                var source = new DirectoryInfo(currentDir + "\\" + AppSettings.UpdateDlTempPath);
                FileAndFolderProcessing.CopyAll(source, target);
                UpdateWorker.ReportProgress(100, LangRes.Get("Complete"));
                var normalLauncher = new Process
                                         {
                                             StartInfo =
                                                 {
                                                     FileName = currentDir + "\\Launcher.exe",
                                                     Arguments = " " + AppSettings.CleanupArgument,
                                                     WorkingDirectory = currentDir
                                                 }
                                         };
                normalLauncher.Start();
            }
            catch (Exception ex)
            {
                ErrorHandling.ShowError(ex.Message);
            }
            finally
            {
                Application.Exit();
            }
        }

        //deleting temp directories
        public void CleanupTemp()
        {
            try
            {
                Directory.Delete(AppSettings.UpdateDlTempPath, true);
            }
            catch (Exception ex)
            {
                ErrorHandling.ShowError(ex.Message);
            }
        }

        #endregion
    }
}
